#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include "net.h"

#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif

static int net_udp_sock_init(unsigned short port);
static int connec_with_timeout(int fd, struct sockaddr *addr, socklen_t addt_len, struct timeval *tm);

const char *net_version()
{
    static const char *version = "libnet V0.1 build@"__DATE__" "__TIME__;
    return version;
}

int net_tcp_client_init(const char *ip, unsigned short port, struct timeval *timeout)
{
    int sockfd = -1;
    int ret = -1;
    struct sockaddr_in client_addr;

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        printf("socket create sockfd[%d] error! errno[%d] %s!\n", sockfd, errno, strerror(errno));
        return sockfd;
    }

    bzero(&client_addr, sizeof(client_addr));
    client_addr.sin_family = AF_INET;
    client_addr.sin_port = htons(port);
    client_addr.sin_addr.s_addr = inet_addr(ip);
    if (NULL == timeout)
    {
        ret = connect(sockfd, (struct sockaddr *)&client_addr, sizeof(client_addr));
    }
    else
    {
        ret = connec_with_timeout(sockfd, (struct sockaddr *)&client_addr, sizeof(client_addr), timeout);
    }
    if (ret != 0)
    {
        printf("connect [%s:%d] error! errno[%d] %s!\n", ip, port, errno, strerror(errno));
        close(sockfd);
        sockfd = -1;
        return sockfd;
    }

    return sockfd;
}

int net_tcp_client_init_v6(const char *ip, unsigned short port, struct timeval *timeout)
{
    int sockfd = -1;
    int ret = -1;
    struct sockaddr_in6 client_addr;

    sockfd = socket(AF_INET6, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        printf("socket create sockfd[%d] error! errno[%d] %s!\n", sockfd, errno, strerror(errno));
        return sockfd;
    }

    bzero(&client_addr, sizeof(client_addr));
    client_addr.sin6_family = AF_INET6;
    client_addr.sin6_port = htons(port);
    ret = inet_pton(AF_INET6, ip, &client_addr.sin6_addr);
    printf("ret = [%d]\n", ret);

    if (NULL == timeout)
    {
        ret = connect(sockfd, (struct sockaddr *)&client_addr, sizeof(client_addr));
    }
    else
    {
        ret = connec_with_timeout(sockfd, (struct sockaddr *)&client_addr, sizeof(client_addr), timeout);
    }
    if (ret != 0)
    {
        printf("connect [%s:%d] error! errno[%d] %s!\n", ip, port, errno, strerror(errno));
        close(sockfd);
        sockfd = -1;
        return sockfd;
    }

    return sockfd;
}

int net_tcp_server_init(unsigned short port, unsigned int listen_num)
{
    int sockfd = -1;
    int ret = -1;
    int opt = 1;
    struct sockaddr_in addr;

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        printf("socket create sockfd[%d] error! errno[%d] %s!\n", sockfd, errno, strerror(errno));
        return sockfd;
    }

    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);

    // 端口复用
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    ret = bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
    if (ret != 0)
    {
        printf("bind [%d] error! errno[%d] %s!\n", port, errno, strerror(errno));
        close(sockfd);
        sockfd = -1;
        return sockfd;
    }

    ret = listen(sockfd, listen_num);
    if (ret != 0)
    {
        printf("listen [%d] error! errno[%d] %s!\n", port, errno, strerror(errno));
        close(sockfd);
        sockfd = -1;
        return sockfd;
    }

    return sockfd;
}

int net_tcp_server_init_v6(unsigned short port, unsigned int listen_num)
{
    int sockfd = -1;
    int ret = -1;
    int opt = 1;
    struct sockaddr_in6 addr;

    sockfd = socket(AF_INET6, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        printf("socket create sockfd[%d] error! errno[%d] %s!\n", sockfd, errno, strerror(errno));
        return sockfd;
    }

    bzero(&addr, sizeof(addr));
    addr.sin6_family = AF_INET6;
    addr.sin6_port = htons(port);
    addr.sin6_addr = in6addr_any;

    // 端口复用
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    ret = bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
    if (ret != 0)
    {
        printf("bind [%d] error! errno[%d] %s!\n", port, errno, strerror(errno));
        close(sockfd);
        sockfd = -1;
        return sockfd;
    }

    ret = listen(sockfd, listen_num);
    if (ret != 0)
    {
        printf("listen [%d] error! errno[%d] %s!\n", port, errno, strerror(errno));
        close(sockfd);
        sockfd = -1;
        return sockfd;
    }

    return sockfd;
}

int net_udp_client_init()
{
    return net_udp_sock_init(0);
}

int net_udp_server_init(unsigned short port)
{
    return net_udp_sock_init(port);
}

int set_socket_recv_timeout(int sockfd, float sec)
{
    struct timeval timeout;
    timeout.tv_sec = (int)sec;
    timeout.tv_usec = (int)(sec - timeout.tv_sec) * 1000000;
    return setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
}

int set_socket_send_timeout(int sockfd, float sec)
{
    struct timeval timeout;
    timeout.tv_sec = (int)sec;
    timeout.tv_usec = (int)(sec - timeout.tv_sec) * 1000000;
    return setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
}

static int net_udp_sock_init(unsigned short port)
{
    int sockfd = -1;
    int ret = -1;
    struct sockaddr_in addr;

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        printf("socket create sockfd[%d] error! errno[%d] %s!\n", sockfd, errno, strerror(errno));
        return sockfd;
    }

    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);

    ret = bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
    if (ret != 0)
    {
        printf("bind error! sockfd[%d] port[%d] errno[%d] %s!\n", sockfd, port, errno, strerror(errno));
        close(sockfd);
        sockfd = -1;
        return sockfd;
    }

    return sockfd;
}

static int connec_with_timeout(int fd, struct sockaddr *addr, socklen_t addt_len, struct timeval *tm)
{
    int ret = -1;
    int err = -1;
    int flag = 0;
    socklen_t len;
    fd_set wfds;
    fd_set rfds;
    struct timeval timeout;
    bzero(&timeout, sizeof(timeout));

    memcpy(&timeout, tm, sizeof(struct timeval));
    printf("timeout = %ld, %ld\n", timeout.tv_sec, timeout.tv_usec);

    flag = 1;
    ioctl(fd, FIONBIO, &flag); /* 设置 socket 非阻塞 */
    ret = connect(fd, addr, addt_len);
    if (-1 == ret)
    {
        if (EINPROGRESS == errno)
        {
            FD_ZERO(&wfds);
            FD_ZERO(&rfds);
            FD_SET(fd, &wfds);
            FD_SET(fd, &rfds);
            if (select(fd + 1, &rfds, &wfds, NULL, &timeout) > 0)
            {
#if defined UNIX
                (void)getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len);
                if (0 == err)
                {
                    ret = 0;
                }
                else
                {
                    ret = -1;
                }
#else
                if (FD_ISSET(fd, &rfds) || FD_ISSET(fd, &wfds))
                {
                    ret = connect(fd, addr, addt_len);
                    if (errno != EISCONN && ret != 0)
                    {
                        ret = -1;
                    }
                }
#endif
            }
            else
            {
                /* select 超时 */
                ret = -1;
            }
        }
    }
    else
    {
        ret = 0;
    }

    flag = 0;
    ioctl(fd, FIONBIO, &flag); /* 设置阻塞 */

    return ret;
}


/**
 * @fn        unsigned int ip2i(const char *ip)
 * @brief     将点分十进制IP地址转换为网络字节序格式
 *
 * @param     [in ] ip                      点分十进制格式的字符串IP
 *
 * @return    unsigned int                  网络字节序格式 UINT32 IP
 */
unsigned int ip2i(const char *ip)
{
    if (NULL == ip)
    {
        return 0;
    }

    struct in_addr addr;
    memset(&addr, 0, sizeof(addr));

    inet_pton(AF_INET, ip, &addr);
    return addr.s_addr;
}

/**
 * @fn        char *i2ip(char *ip, int len, unsigned int iaddr)
 * @brief     将网络字节序IP地址转换为点分十进制格式的字符串
 *
 * @param     [out] ip                      转换后 IP 缓冲区
 * @param     [in ] len                     缓冲区长度
 * @param     [in ] iaddr                   网络字节序 UINT32 IP 地址
 *
 * @return    char*                         返回 IP 缓冲区地址
 */
char *i2ip(char *ip, int len, unsigned int iaddr)
{
    if (NULL == ip || len < 16)
    {
        return NULL;
    }

    char tmpIp[16] = {0}; // 足够存储一个IPv4地址的字符串表示，包括终止符'\0'
    struct in_addr addr;
    addr.s_addr = iaddr;

    inet_ntop(AF_INET, &addr, tmpIp, sizeof(tmpIp));
    snprintf(ip, MIN(strlen(tmpIp) + 1, len), "%s", tmpIp);

    return ip;
}

/**
 * @fn        int SetSocketHeartBeat(int _fd, int enable, int idle, int time, int cnt)
 * @brief     设置 TCP sockfd 心跳保活
 *
 * @param     [in] _fd       sockfd 套接字
 * @param     [in] enable    心跳开关 0 关，非 0 开
 * @param     [in] idle      建立连接没有数据发送，首次发送心跳时间，0 立即开始发送心跳
 * @param     [in] time      心跳间隔 秒
 * @param     [in] cnt       给定次数收不到心跳视为断开连接
 *
 * @return    int            成功返回 0，失败返回 -1
 */
int SetSocketHeartBeat(int _fd, int enable, int idle, int time, int cnt)
{
    /* 设定 socket TCP 心跳 */
    /* #include <netinet/tcp.h> */
    int keepAlive = enable;  /* 设定KeepAlive 开启心跳 */
    int keepIdle = idle;     /* 开始首次KeepAlive探测前的TCP空闭时间 */
    int keepInterval = time; /* 两次KeepAlive探测间的时间间隔 秒 */
    int keepCount = cnt;     /* 判定断开前的KeepAlive探测次数，几次收不到心跳就判断断开 */
    int retVal = 0;
    retVal = setsockopt(_fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));
    if (-1 == retVal)
    {
        return retVal;
    }
    retVal = setsockopt(_fd, SOL_TCP, TCP_KEEPIDLE, (void *)&keepIdle, sizeof(keepIdle));
    if (-1 == retVal)
    {
        return retVal;
    }
    retVal = setsockopt(_fd, SOL_TCP, TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
    if (-1 == retVal)
    {
        return retVal;
    }
    retVal = setsockopt(_fd, SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));
    if (-1 == retVal)
    {
        return retVal;
    }
    return retVal;
}

ssize_t readn(int fd, void *vptr, size_t n)
{
    size_t nleft;
    ssize_t nread;
    char *ptr;
    ptr = vptr;
    nleft = n;
    while (nleft > 0)
    {
        if ((nread = read(fd, ptr, nleft)) < 0)
        {
            if (errno == EINTR)
                nread = 0;
            else
                return (-1);
        }
        else if (nread == 0)
            break;
        nleft -= nread;
        ptr += nread;
    }
    return (n - nleft);
}

ssize_t writen(int fd, const void *vptr, size_t n)
{
    size_t nleft;
    ssize_t nwritten;
    const char *ptr;
    ptr = vptr;
    nleft = n;
    while (nleft > 0)
    {
        if (nwritten = write(fd, ptr, nleft) <= 0)
        {
            if (nwritten < 0 && errno == EINTR)
                nwritten = 0;
            else
                return (-1);
        }
        nleft -= nwritten;
        ptr += nwritten;
    }
    return (n);
}